package oojo.data.impl.exec;

import java.util.List;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import oojo.data.DataPage;
import oojo.data.Query;
import oojo.data.impl.BaseFilter;
import oojo.data.impl.BaseQuery;
import oojo.data.impl.Properties;
import oojo.data.spec.Fetch;

public class FetchQuery<E> extends BaseQuery<E, DataPage<E>> {

    private static final Logger logger = LoggerFactory.getLogger(FetchQuery.class);
    private static final Pattern orderPattern = Pattern.compile("(^|,) *");
    
    public Class<?> getQueryClass() {
        return Fetch.class;
    }
    
    public DataPage<E> execute(Query<E, DataPage<E>> fetch) {
        logger.debug("---> execute(fetch)");
        
        Fetch<E> spec = (Fetch<E>) fetch;
        DataPage<E> page = new DataPage<E>();
        page.setData(fetchList(spec));
        page.setTotal(countTotal(spec));
        return page;
    }
    
    @SuppressWarnings("unchecked")
    protected List<E> fetchList(Fetch<E> spec) {
        logger.debug("---> fetchList(spec)");
        
        StringBuilder sb = new StringBuilder();
        sb.append("select e ").append(createFrom(spec));
        
        for (String join : spec.getJoins()) {
            sb.append(" left join fetch e.").append(join);
        }
        
        String criteria = createCriteria(spec);
        
        if (criteria != null && criteria.length() > 0) {
            sb.append(" where ").append(criteria);
        }
        
        String order = spec.getOrder();
        
        if (order != null && order.length() > 0) {
            sb.append(" order by ").append(orderPattern.matcher(order).replaceAll("$0e."));
        } else if (spec.getLimit() > 0) {
            String idProperty = Properties.idProperty(getRepository().getEntityClass());
            sb.append(" order by e.").append(idProperty);
        }
        
        logger.debug(sb.toString());
        
        javax.persistence.Query query = getRepository().getEntityManager().createQuery(sb.toString());
        configCriteria(spec, query);
        
        if (spec.getOffset() > 0) {
            query.setFirstResult(spec.getOffset());
        }
        
        if (spec.getLimit() > 0) {
            query.setMaxResults(spec.getLimit());
        }
        
        return query.getResultList();
    }
    
    protected Long countTotal(Fetch<E> spec) {
        logger.debug("---> countTotal(spec)");
        
        StringBuilder sb = new StringBuilder();
        sb.append("select count(e) ").append(createFrom(spec));
        
        String criteria = createCriteria(spec);
        
        if (criteria != null && criteria.length() > 0) {
            sb.append(" where ").append(criteria);
        }
        
        logger.debug(sb.toString());
        
        javax.persistence.Query query = getRepository().getEntityManager().createQuery(sb.toString());
        configCriteria(spec, query);
        
        return (Long) query.getSingleResult();
    }
    
    protected String createFrom(Fetch<E> spec) {
        logger.debug("---> createFrom(spec)");
        
        return "from " + getRepository().getEntityClass().getName() + " e";
    }
    
    protected String createCriteria(Fetch<E> spec) {
        logger.debug("---> createCriteria(spec)");
        
        if (spec.getFilter() != null) {
            BaseFilter filter = getRepository().getFilters().get(spec.getFilter().getClass()); 
            return filter.createCriteria(spec.getFilter());
        }
        
        return null;
    }
    
    protected void configCriteria(Fetch<E> spec, javax.persistence.Query query) {
        logger.debug("---> configCriteria(spec, query)");
        
        if (spec.getFilter() != null) {
            BaseFilter filter = getRepository().getFilters().get(spec.getFilter().getClass());
            filter.configCriteria(spec.getFilter(), query);
        }
    }
}
